using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace Magiq.Support {
    public class ReflectionBuilder {
        private static readonly Dictionary<Type, OpCode> valueTypesOpCodes;

        static ReflectionBuilder() {
            valueTypesOpCodes = new Dictionary<Type, OpCode>
                                    {
                                        {typeof (sbyte), OpCodes.Ldind_I1},
                                        {typeof (byte), OpCodes.Ldind_U1},
                                        {typeof (char), OpCodes.Ldind_U2},
                                        {typeof (short), OpCodes.Ldind_I2},
                                        {typeof (ushort), OpCodes.Ldind_U2},
                                        {typeof (int), OpCodes.Ldind_I4},
                                        {typeof (uint), OpCodes.Ldind_U4},
                                        {typeof (long), OpCodes.Ldind_I8},
                                        {typeof (ulong), OpCodes.Ldind_I8},
                                        {typeof (bool), OpCodes.Ldind_I1},
                                        {typeof (double), OpCodes.Ldind_R8},
                                        {typeof (float), OpCodes.Ldind_R4}
                                    };
        }

        public Action<object, object> PropertySetter(Type type, string property) {
            var m = new DynamicMethod("Set" + property,
                                      typeof (void), new[] {typeof (object), typeof (object)},
                                      type, true);

            var info = type.GetProperty(property);

            var ilgen = m.GetILGenerator();
            ilgen.DeclareLocal(info.PropertyType);
            ilgen.Emit(OpCodes.Ldarg_0); //Load the first argument 

            //(target object)

            //Cast to the source type

            ilgen.Emit(OpCodes.Castclass, type);
            ilgen.Emit(OpCodes.Ldarg_1); //Load the second argument 

            //(value object)

            if (info.PropertyType.IsValueType) {
                ilgen.Emit(OpCodes.Unbox, info.PropertyType); //Unbox it 

                if (valueTypesOpCodes.ContainsKey(info.PropertyType)) //and load
                {
                    var load = valueTypesOpCodes[info.PropertyType];
                    ilgen.Emit(load);
                }
                else {
                    ilgen.Emit(OpCodes.Ldobj, info.PropertyType);
                }
            }
            else {
                ilgen.Emit(OpCodes.Castclass, info.PropertyType); //Cast class
            }

            var set = info.GetSetMethod(true);
            if (set.IsVirtual)
                ilgen.Emit(OpCodes.Callvirt, set);
            else
                ilgen.Emit(OpCodes.Call, set);

            //return
            ilgen.Emit(OpCodes.Ret);


            return (Action<object, object>) m.CreateDelegate(typeof (Action<object, object>));
        }

        public Func<object, object> FieldGetter(Type type, string field) {
            var d = new DynamicMethod("Get" + field, typeof (object), new[] {typeof (object)}, type, true);

            var info = type.GetField(field, BindingFlags.Instance | BindingFlags.NonPublic);
            // Get the IL generator for the method
            var ilgen = d.GetILGenerator();


            // load obj
            ilgen.Emit(OpCodes.Ldarg_0);

            // loads the value of the field
            ilgen.Emit(OpCodes.Ldfld, info);

            if (info.FieldType.IsValueType)
                ilgen.Emit(OpCodes.Box, info.FieldType); //Box it
            else
                ilgen.Emit(OpCodes.Castclass, typeof (object)); //Cast class

            //return
            ilgen.Emit(OpCodes.Ret);

            return (Func<object, object>) d.CreateDelegate(typeof (Func<object, object>));
        }

        public Func<object, object> PropertyGetter(Type type, string property) {
            var d = new DynamicMethod("Get" + property, typeof (object), new[] {typeof (object)}, type, true);

            var info = type.GetProperty(property);

            var getter = info.GetGetMethod(true);

            // Get the IL generator for the method
            var ilgen = d.GetILGenerator();

            // Declare p (loc0)
            ilgen.DeclareLocal(type);

            // load obj
            ilgen.Emit(OpCodes.Ldarg_0);
            // cast obj
            ilgen.Emit(OpCodes.Castclass, type);
            // stores the result in p
            ilgen.Emit(OpCodes.Stloc_0);

            // load p
            ilgen.Emit(OpCodes.Ldloc_0);
            // loads the value of the property
            ilgen.EmitCall(OpCodes.Call, getter, null);

            if (info.PropertyType.IsValueType)
                ilgen.Emit(OpCodes.Box, info.PropertyType); //Box it
            else
                ilgen.Emit(OpCodes.Castclass, typeof (object)); //Cast class

            //return
            ilgen.Emit(OpCodes.Ret);

            return (Func<object, object>) d.CreateDelegate(typeof (Func<object, object>));
        }
    }
}